using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Text.RegularExpressions;
using System.IO;
using System.Diagnostics;

namespace SqlBlocks
{
	/// <summary>
	/// A event handler delegate for the Runner class
	/// </summary>
	/// <param name="message">A status message</param>
	/// <param name="details">Message details</param>
	public delegate void RunnerEventDelegate(int depth, string message, IEnumerable<string> details);

	/// <summary>
	/// This is a utility class for running migrations
	/// </summary>
	public static class Runner
	{
		public static string ConnectionString;
		public static Drivers.IDatabaseDriver Driver = new SqlBlocks.Drivers.SqlServer();
        public static event RunnerEventDelegate Event;

        //static void FireEvent(string message)
        //{
        //    FireEvent(0, message);
        //}
        static void FireEvent(int depth, string message)
        {
            FireEvent(depth, message, null);
        }
        static void FireEvent(int depth, string message, IEnumerable<string> details)
        {
            if (Runner.Event != null)
            {
                Runner.Event(depth, message, details);
            }
        }

		#region Database driver/connection string helpers

		#endregion

		#region Migrations project helpers (Build, run, etc)

        /// <summary>
        /// Runs migrations found in the specified directory.  Searches for both a project and SQL file migrations,
        /// and migrates the database to the latest version.
        /// </summary>
        /// <param name="path">The migrations project path</param>
        public static void Run(string path)
        {
            Run(path, MigrationFilter.Default);
        }

		/// <summary>
		/// Runs migrations found in the specified directory.  Searches for both a project and SQL file migrations,
		/// and migrates the database to the specific version.
		/// </summary>
		/// <param name="path">The migrations project path</param>
		/// <param name="targetVersion">The target database version</param>
		public static void Run(string path, MigrationFilter filter)
		{
			DirectoryInfo dir = new DirectoryInfo(path);
			FileInfo projectFile = new FileInfo(path);
			if (!dir.Exists)
			{
                FireEvent(0, "Specified path is a file, assuming it's a project", null);
				if (!projectFile.Exists)
				{
					throw new FileNotFoundException("The specified path was not found");
				}
				dir = projectFile.Directory;
			}

			Assembly assembly = null;
			if (projectFile != null && projectFile.Exists)
			{
				assembly = AssemblyHelper.GetProjectAssembly(projectFile.FullName);
			}

			Run(assembly, dir.FullName, filter);
		}
		/// <summary>
		/// Runs a set of migrations gathered from the specified assembly and path
		/// </summary>
		/// <param name="assembly">The assembly to search for migrations</param>
		/// <param name="path">The path to search for SQL migrations</param>
		public static void Run(Assembly assembly, string path, MigrationFilter filter)
		{
			List<MigrationDescription> migrations = GetMigrations(assembly, path);

			double currentVersion = Driver.CheckVersion(ConnectionString);

            double targetVersion = filter.Version;

			bool reverse = targetVersion > -1 && targetVersion < currentVersion;

			if (reverse)
			{
				migrations.Reverse();
			}

			for (int i = 0; i < migrations.Count; i++)
			{
                double migVersion = migrations[i].Version;
				if (migVersion > currentVersion && (migVersion <= targetVersion || targetVersion == -1) && !reverse)
				{
                    FireEvent(0, string.Format("Migrating to version {0}: {1}", migrations[i].Version, migrations[i].Name));
					Changeset db = new Changeset();
					migrations[i].Migration.Up(db);
					Driver.Commit(db, migrations[i].Version);
				}
				else if (migVersion <= currentVersion && migVersion > targetVersion && reverse)
				{
					FireEvent(0, string.Format("Rolling back version {0}: {1}", migrations[i].Version, migrations[i].Name));
					Changeset db = new Changeset();
					migrations[i].Migration.Down(db);
					Driver.Commit(db, migrations[i].Version - 1);
				}
			}

			int count = migrations.Count;
		}
		#endregion

		#region Migration location helpers
		/// <summary>
		/// Gets migrations from an assembly and project directory
		/// </summary>
		/// <param name="assembly">The assembly of Migration classes</param>
		/// <param name="path">The base project directory</param>
		/// <returns>A sorted list of migrations</returns>
		public static List<MigrationDescription> GetMigrations(Assembly assembly, string path)
		{
			List<MigrationDescription> migrations = new List<MigrationDescription>();
            if (!string.IsNullOrEmpty(path))
            {
                FireEvent(0,"Searching for SQL migrations");
                migrations.AddRange(FileHelper.GetMigrations(path).Complete);
                FireEvent(1,string.Format("...found {0} migration(s)", migrations.Count));
            }
			if (assembly != null)
			{
                int count = migrations.Count;
                FireEvent(0,"Searching for assembly migrations");
				migrations.AddRange(AssemblyHelper.GetMigrations(assembly));
                FireEvent(1,string.Format("...found {0} migration(s)", migrations.Count - count));
			}
			migrations.Sort();
			return migrations;
		}
		#endregion

        #region Schema dumper
        public static void DumpSchema(string outPath)
        {
            FireEvent(0, string.Format("Dumping database schema to {0}", outPath));
            string sql = Runner.Driver.DumpSchema(Runner.ConnectionString);

            
            File.WriteAllText(outPath, sql);
        }

        //public static void LoadSchema(string inPath)
        //{
        //    FireEvent(0, string.Format("Loading schema from {0}", inPath));
        //    string schema = File.ReadAllText(inPath);
        //    Runner.Driver.LoadSchema(Runner.ConnectionString, schema);
        //}
        #endregion
    }	
}
